﻿using System;
using System.Collections.Generic;

namespace NamedBinaryTag
{
	/// <summary>
	/// Represents a Tag containing a list of Tags of the same type.
	/// </summary>
	public sealed class TagList : NamedTag
	{
		private byte _listType;

		/// <summary>
		/// Obtains or defines the type of the inner Tags.
		/// </summary>
		/// <remarks>
		/// Defining the type of the inner tags will clear the collection.
		/// </remarks>
		public byte ListType
		{
			get { return _listType; }
			set { if (_listType != value) { _listType = value; RaisePropertyChange("Value"); Items.Clear(); } }
		}

		/// <summary>
		/// Obtains the collection of inner Tags.
		/// </summary>
		public System.Collections.ObjectModel.ObservableCollection<ITag> Items { get; private set; }

		public override byte TagType
		{
			get { return 9; }
		}

		public TagList()
		{
			Items = new FilteredObservableCollection<ITag>(this);
		}

		public override void Read(System.IO.BinaryReader input, bool ignoreName)
		{
			base.Read(input, ignoreName);
			TagByte type = new TagByte();
			type.Read(input, true);
			ListType = type.Value;
			TagInt length = new TagInt();
			length.Read(input, true);
			IList<ITag> items = new List<ITag>();
			for (int i = 0; i < length.Value; i++)
			{
				ITag element = TagFactory.CreateTag(ListType);
				if (element is NamedTag)
					(element as NamedTag).Read(input, true);
				else
					element.Read(input);
				items.Add(element);
			}
			Items.Clear();
			foreach (ITag item in items)
				Items.Add(item);
		}

		public override void Write(System.IO.BinaryWriter output, bool ignoreName)
		{
			base.Write(output, ignoreName);
			TagByte type = new TagByte();
			type.Value = ListType;
			type.Write(output, true);
			TagInt length = new TagInt();
			length.Value = Items.Count;
			length.Write(output, true);
			foreach (ITag element in Items)
			{
				if (element is NamedTag)
					(element as NamedTag).Write(output, true);
				else
					element.Write(output);
			}
		}

		/// <summary>
		/// Defines an ObservableCollection with filtering on the containing list tag type id.
		/// </summary>
		private class FilteredObservableCollection<T> : System.Collections.ObjectModel.ObservableCollection<T> where T : ITag
		{
			private TagList parent;
			public FilteredObservableCollection(TagList parent)
			{
				this.parent = parent;
			}

			protected override void InsertItem(int index, T item)
			{
				if (item.TagType != parent.ListType)
					throw new ArgumentException(string.Format("Invalid type for the item. Expected {0}, found {1}.", parent.ListType, item.TagType), "item");
				base.InsertItem(index, item);
			}

			protected override void SetItem(int index, T item)
			{
				if (item.TagType != parent.ListType)
					throw new ArgumentException(string.Format("Invalid type for the item. Expected {0}, found {1}.", parent.ListType, item.TagType), "item");
				base.SetItem(index, item);
			}
		}

		public override string ToString()
		{
			return string.Format("TagList {{ Name = \"{0}\", Type = {1}, Count = {2} }}", Name, _listType, Items.Count);
		}
	}
}
